--[[

   Copyright (C) 2007 MySQL AB

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; version 2 of the License.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

   Written by Charles Cahoon during Summer of Code 2007
--]]


module("macro_util", package.seeall)

require("mytokenizer")

----------------------------
-- Macro Utilities Module --
----------------------------

local macro_table = {}
local num_macros = 0
local proxy
local packet
local query_table
local loop = nil
local loop_count = nil
local loop_counter_name = nil
local loop_iterations = nil

-----------------
-- Macro Class --
-----------------

Macro = {
  definition = ''  -- got from configuration file
}

function Macro:new(o)
  o = o or {}
  setmetatable(o, self)
  self.__index = self
  return o
end

function Macro:description(d)
  if d then
      self.descr = d
  end
  return self.descr
end


function Macro:name(n,d)
  if n then
      self.definition = n
  end
  if d then
      self.descr = d
  end
  return self.definition, self.descr
end

function Macro:action(param)
  print_verbose("I do nothing")
end

--LIST OF SUBMACROS
StartTesting = Macro:new()
StopTesting = Macro:new()
EnableWarnings = Macro:new()
DisableWarnings = Macro:new()
Drop = Macro:new()
View = Macro:new()
PrintFiles = Macro:new()
RenameMacro = Macro:new()
Include = Macro:new()
ToggleErrors = Macro:new()
Reset = Macro:new()
ToTest = Macro:new()
StartLoop = Macro:new()
StopLoop = Macro:new()
Macro_Help = Macro:new()
NoErrors = Macro:new()
DiscardLast = Macro:new()

--SUBMACRO VARIABLES
local start_testing = StartTesting:new()
local stop_testing = StopTesting:new()
local enable_warnings = EnableWarnings:new()
local disable_warnings = DisableWarnings:new()
local drop = Drop:new()
local view = View:new()
local print_files = PrintFiles:new()
local rename_macro = RenameMacro:new()
local include = Include:new()
local toggle_errors = ToggleErrors:new()
local reset = Reset:new()
local to_test = ToTest:new()
local start_loop = StartLoop:new()
local stop_loop = StopLoop:new()
local macro_help = Macro_Help:new()
local no_errors = NoErrors:new()
local discard_last = DiscardLast:new()

--SUBMACRO NAME INITIALIZATION
start_testing:name("START",'starts the test')
stop_testing:name("STOP", 'stops the test')
enable_warnings:name("ENABLE_WARNINGS",'adds "enable warnings" to test case')
disable_warnings:name("DISABLE_WARNINGS",'adds "disable warnings" to test case')
drop:name("CREATE",'adds DROP object before a CREATE')
view:name("VIEW",'shows the test or result')
print_files:name("WRITE",'writes to the test script')
rename_macro:name("RENAME",'renames a macro')
include:name("INCLUDE",'adds an include file to the test')
toggle_errors:name("TOGGLE_ERRORS",'if this switch is on errors will not be logged to the test case')
reset:name("RESET",'restarts the test. previous lines are discarded')
to_test:name("TO_TEST", 'sets test case name')
start_loop:name("START_LOOP",'starts a loop')
stop_loop:name("STOP_LOOP",'stops a loop')
macro_help:name("MACRO_HELP",'show this help')
no_errors:name("NO_ERRORS", 'removes all errors from test case')
discard_last:name("DISCARD_LAST", 'discards the last query from the test case')

function print_verbose (msg)
   if proxy.global["verbose"] == "1" or proxy.global["verbose"] == "2" then
       print (msg)
   end
end

--DEFINED SUBMACRO ACTIONS
function StartTesting:action(param)
       proxy.global["start_testing"] = true

       print_verbose ("Testing has started")
       if param[2] ~= nil and param[3] == nil then
               proxy.global["logs"].set_test_name(param[2])
       end
       packet = nil
       proxy.response.type = proxy.MYSQLD_PACKET_OK
       proxy.response.resultset = {
               fields = {
                       {type = proxy.MYSQL_TYPE_STRING, name = "TEST CREATOR",},
               },
               rows = {
                       {"Testing has started..."}
               }
       }
end

function Macro_Help:action(param)
       print_verbose ("macro help requested")
       packet = nil
       proxy.response.type = proxy.MYSQLD_PACKET_OK
   local help_dataset = {}
   for i,v in pairs(macro_table) do
       table.insert(help_dataset, { v:name(), v:description() } )
       -- print (v:name(), v:description())
   end
       proxy.response.resultset = {
               fields = {
                       {type = proxy.MYSQL_TYPE_STRING, name = "MACRO ",},
                       {type = proxy.MYSQL_TYPE_STRING, name = "description",},
               },
               rows = help_dataset
       }
       proxy.global["logs"].write_to_files()
end


function StopTesting:action(param)
       proxy.global["start_testing"] = false
       print_verbose ("Testing has ended")
       if param[2] ~= nil and param[3] == nil then
               proxy.global["logs"].set_test_name(param[2])
       end
       packet = nil
       proxy.response.type = proxy.MYSQLD_PACKET_OK
       proxy.response.resultset = {
               fields = {
                       {type = proxy.MYSQL_TYPE_STRING, name = "TEST CREATOR",},
               },
               rows = {
                       {"Testing has stopped"}
               }
       }
       proxy.global["logs"].write_to_files()
end

function EnableWarnings:action(param)
       proxy.global["logs"].write_to_test("--enable_warnings", proxy.connection.server.thread_id)
       print_verbose("Warnings enabled")
       packet = nil
       proxy.response.type = proxy.MYSQLD_PACKET_OK
       proxy.response.resultset = {
               fields = {
                       {type = proxy.MYSQL_TYPE_STRING, name = "TEST CREATOR",},
               },
               rows = {
                       {"--enable_warnings written to test"}
               }
       }
end

function DisableWarnings:action(param)
       proxy.global["logs"].write_to_test("--disable_warnings", proxy.connection.server.thread_id)
       print_verbose("Warnings disabled")
       packet = nil
       proxy.response.type = proxy.MYSQLD_PACKET_OK
       proxy.response.resultset = {
               fields = {
                       {type = proxy.MYSQL_TYPE_STRING, name = "TEST CREATOR",},
               },
               rows = {
                       {"--disable_warnings written to test"}
               }
       }
end

function Drop:action(param)
       if proxy.global["start_testing"] == true then
               if string.upper(param[2]) == "DATABASE" then
                       proxy.global["logs"].write_to_test_front("--enable_warnings")
                       proxy.global["logs"].write_to_test_front("DROP DATABASE if exists " .. param[3] .. ";")
                       proxy.global["logs"].write_to_test_front("--disable_warnings")
                       proxy.global["logs"].write_to_result_front("DROP DATABASE if exists " .. param[3] .. ";")
               elseif string.upper(param[2]) == "TABLE" then
                       proxy.global["logs"].write_to_test_front("--enable_warnings")
                       proxy.global["logs"].write_to_test_front("DROP TABLE if exists " .. param[3] .. ";")
                       proxy.global["logs"].write_to_test_front("--disable_warnings")
                       proxy.global["logs"].write_to_result_front("DROP TABLE if exists " .. param[3] .. ";")
               end
       end
end

function View:action(param)
       if string.upper(param[2]) == "TEST" then
               local test, test_length = proxy.global["logs"].get_test()
               local output = {}
               for i=1,test_length-1 do
                       output[i] = {test[i]}
               end
               proxy.response.type = proxy.MYSQLD_PACKET_OK
               proxy.response.resultset = {
                       fields = {
                               {type = proxy.MYSQL_TYPE_STRING, name = "Test File",}
                       },
                       rows = output
               }
               packet = nil
       elseif string.upper(param[2]) == "RESULT" then
               local result, result_length = proxy.global["logs"].get_result()
               local output = {}
               for i=1,result_length-1 do
                       output[i] = {result[i]}
               end
               proxy.response.type = proxy.MYSQLD_PACKET_OK
               proxy.response.resultset = {
                       fields = {
                               {type = proxy.MYSQL_TYPE_STRING, name = "Result File",}
                       },
                       rows = output
               }
               packet = nil
       end
end

function PrintFiles:action(param)
       local name = "creator"
       if param[2] == nil then
               proxy.global["logs"].write_to_files()
       else
               name = param[2]
               proxy.global["logs"].set_test_name(name)
               proxy.global["logs"].write_to_files()
       end
       packet = nil
       proxy.response.type = proxy.MYSQLD_PACKET_OK
       proxy.response.resultset = {
               fields = {
                       {type = proxy.MYSQL_TYPE_STRING, name = "TEST CREATOR",},
               },
               rows = {
                       {"test files written to " .. name .. ".test and " .. name .. ".result"}
               }
       }
end

function RenameMacro:action(param)
       local macro_replaced = false
       if param[2] ~= nil and param[3] ~= nil and param[4] == nil then
               for m_i, m_v in pairs(macro_table) do
                       if string.upper(param[2]) == string.upper(m_v:name()) then
                               m_v:name(string.upper(param[3]))
                               macro_replaced = true
                       end
               end
               if macro_replaced == true then
                       packet = nil
                       proxy.response.type = proxy.MYSQLD_PACKET_OK
                       proxy.response.resultset = {
                               fields = {
                                       {type = proxy.MYSQL_TYPE_STRING, name = "ORIGINAL MACRO"},
                                       {type = proxy.MYSQL_TYPE_STRING, name = "NEW MACRO"},
                               },
                               rows = {
                                       {param[2], param[3]}
                               }
                       }
               end
       end
end

function Include:action(param)
       if param[2] ~= nil and param[3] == nil then
               proxy.global["logs"].write_to_test_front("--source include/" .. param[2])
               packet = nil
               proxy.response.type = proxy.MYSQLD_PACKET_OK
               proxy.response.resultset = {
                       fields = {
                               {type = proxy.MYSQL_TYPE_STRING, name = "TEST CREATOR"},
                       },
                       rows = {
                               {param[2] .. " has been included to the test file"}
                       }
               }
       end
end

function ToggleErrors:action(param)
       if string.upper(param[2]) == "OFF" and param[3] == nil then
               proxy.global["toggle_errors"] = true
               packet = nil
               proxy.response.type = proxy.MYSQLD_PACKET_OK
               proxy.response.resultset = {
               fields = {
                               {type = proxy.MYSQL_TYPE_STRING, name = "TEST CREATOR"},
                       },
                       rows = {
                               {"Test Creator will not record errors"}
                       }
               }
       end
       if string.upper(param[2]) == "ON" and param[3] == nil then
               proxy.global["toggle_errors"] = false
               packet = nil
               proxy.response.type = proxy.MYSQLD_PACKET_OK
               proxy.response.resultset = {
               fields = {
                               {type = proxy.MYSQL_TYPE_STRING, name = "TEST CREATOR"},
                       },
                       rows = {
                               {"Test Creator will record errors"}
               }
       }
       end
end

function Reset:action(param)
       proxy.global["logs"].reset_tables()
       packet = nil
       proxy.response.type = proxy.MYSQLD_PACKET_OK
       proxy.response.resultset = {
       fields = {
                       {type = proxy.MYSQL_TYPE_STRING, name = "TEST CREATOR"},
               },
               rows = {
                       {"Test case has been reset"}
               }
       }
end

function ToTest:action(param)
       if param[2] ~= nil and param[3] == nil then
               proxy.global["logs"].set_test_name(param[2])
               packet = nil
               proxy.response.type = proxy.MYSQLD_PACKET_OK
               proxy.response.resultset = {
                       fields = {
                               {type = proxy.MYSQL_TYPE_STRING, name = "TEST CREATOR"},
                       },
                       rows = {
                               {"Test name has been set to " .. param[2]}
                       }
               }
       end
end

function StartLoop:action(param)
	if loop ~= nil then
		packet = nil
	   	proxy.response.type = proxy.MYSQLD_PACKET_OK
      	proxy.response.resultset = {
	        fields = {
	                {type = proxy.MYSQL_TYPE_STRING, name = "TEST CREATOR"},
	        },
	        rows = {
	                {"ERROR: Loop recording is already in progress"}
	        }
        }      
	else
       	if param[2] ~= nil and param[3] ~= nil and param[4] == nil then
   		   	proxy.global["looping"] = true
   		   	loop = {}
         	loop_count = 1
            loop_counter_name = param[2]
			proxy.global['loop_counter_name'] = loop_counter_name
            loop_iterations = param[3]
            proxy.global["logs"].write_to_test("let $" .. loop_counter_name .. " = " .. loop_iterations .. ";", proxy.connection.server.thread_id)
            proxy.global["logs"].write_to_test("while ($" .. loop_counter_name .. ")", proxy.connection.server.thread_id)
            proxy.global["logs"].write_to_test("{", proxy.connection.server.thread_id)
            packet = nil
            proxy.response.type = proxy.MYSQLD_PACKET_OK
            proxy.response.resultset = {
                    fields = {
                            {type = proxy.MYSQL_TYPE_STRING, name = "TEST CREATOR"},
                    },
                    rows = {
                            {"Beginning loop creation"}
                    }
            }
       	end
	end
end

function StopLoop:action(param)
	   --MAKE SURE START LOOP HAS BEEN CALLED
	   if loop == nil then
	   	   packet = nil
	   	   proxy.response.type = proxy.MYSQLD_PACKET_OK
   	       proxy.response.resultset = {
                   fields = {
                           {type = proxy.MYSQL_TYPE_STRING, name = "TEST CREATOR"},
                   },
                   rows = {
                           {"ERROR: \'START_LOOP\' was not called first"}
                   }
           }      
	   else
		   proxy.global["looping"] = false
		   proxy.global["execute_loop"] = true
	       proxy.global["total_loop"] = {}
	       proxy.global["total_loop_count"] = 1
	       proxy.global["num_queries"] = loop_count - 1
	       proxy.global["iterations"] = loop_iterations
	       proxy.global["logs"].write_to_test("dec $" .. loop_counter_name .. ";", proxy.connection.server.thread_id)
	       proxy.global["logs"].write_to_test("}", proxy.connection.server.thread_id)
	        
	       for i=1, loop_iterations - 1 do
		       for j=1,loop_count-1 do
		       		proxy.global["total_loop"][proxy.global["total_loop_count"]] = loop[j]
		       		proxy.global["total_loop_count"] = proxy.global["total_loop_count"] + 1
		       end
	       end	       
	          
	       proxy.queries:prepend(1, packet)
	       packet = nil 
	       
	       --RESET THE LOOP COUNTERS
	       loop = nil
	       loop_count = nil
	       loop_counter_name = nil
	       loop_iterations = nil 
       end      
end

function NoErrors:action()
	proxy.global["logs"].remove_errors()
	packet = nil
    proxy.response.type = proxy.MYSQLD_PACKET_OK
    proxy.response.resultset = {
            fields = {
                    {type = proxy.MYSQL_TYPE_STRING, name = "TEST CREATOR"},
            },
            rows = {
                    {"All errors have been removed from test case"}
            }
    }
end

function DiscardLast:action()
	proxy.global["logs"].discard_last()
	packet = nil
    proxy.response.type = proxy.MYSQLD_PACKET_OK
    proxy.response.resultset = {
            fields = {
                    {type = proxy.MYSQL_TYPE_STRING, name = "TEST CREATOR"},
            },
            rows = {
                    {"Last query has been discarded"}
            }
    }
end

--Adds macro to table
--@param m: macro to be added to table
--
local function add_macro_to_table(m)
       num_macros = num_macros + 1
       macro_table[num_macros] = m
       if proxy.global["verbose"] == "2" then
               print("macro added: " .. m:name())
       end
end

--Adds all defined macros to table
--
local function init_macros()
       add_macro_to_table(start_testing)
       add_macro_to_table(stop_testing)
       --add_macro_to_table(enable_warnings)
       --add_macro_to_table(disable_warnings)
       add_macro_to_table(drop)
       add_macro_to_table(view)
       --add_macro_to_table(print_files)
       add_macro_to_table(rename_macro)
       add_macro_to_table(include)
       add_macro_to_table(toggle_errors)
       add_macro_to_table(reset)
       add_macro_to_table(to_test)
       add_macro_to_table(start_loop)
       add_macro_to_table(stop_loop)
       add_macro_to_table(macro_help)
       add_macro_to_table(no_errors)
       add_macro_to_table(discard_last)
end

--Creates new MacroList
--@param p: proxy connection, l: test proxy.global["logs"]
--
function create(p)
       proxy = p
       init_macros()
       proxy.global["looping"] = false
end

--Parses command for macros
--@param p: query packet
--
function parse_command(p)
       packet = p
       query_table = {}

       --parse header off packet and tokenize into table
       local query = string.sub(packet, 2)
       local tokenizer = mytokenizer
       tokenizer.create(query)
       local token = tokenizer.next_token()
       local i = 1
       while token ~= nil do
               query_table[i] = token
               i = i + 1
               token = tokenizer.next_token()
       end

       --match tokens to macros
       for i_i, input_value in pairs(query_table) do
               for m_i, m_v in pairs(macro_table) do
                       if string.upper(input_value) == string.upper(m_v:name()) then
                               m_v:action(query_table)
                               query_table = nil
                               return packet
                       end
               end
       end
       
       --if looping
       if proxy.global["looping"] == true then
       		local temp_packet = string.char(proxy.COM_QUERY) .. query
       		loop[loop_count] = temp_packet
       		loop_count = loop_count + 1        		 		
       end
       
       --return packet to send to server, nil to send response
       query_table = nil
       return packet
end
